namespace ScrumSprintMonitor.Modules.WorkItemMonitor.ViewModels
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.Diagnostics;
	using System.Globalization;
	using System.Linq;
	using System.Threading;
	using System.Windows.Media;
	using System.Windows.Threading;

	using Infrastructure;

	using UI.Wpf.Converters;
	using UI.Wpf.ViewModels;

	#endregion

	[DebuggerDisplay("ViewModel: {DisplayName}")]
	public class TeamMemberViewModel : ViewModelBase,
	                                   ITeamMemberViewModel
	{
		static TeamMemberViewModel()
		{
			var dependsOn = Register<TeamMemberViewModel>();

			dependsOn.Call(me => me.OnPropertyChanged("AssignedWorkItems")).OnChanged(me => me.Model.AssignedWorkItems);
			dependsOn.Call(me => me.OnPropertyChanged("DisplayName")).OnChanged(me => me.Model.DisplayName);
			dependsOn.Call(me => me.OnPropertyChanged("AvatarImageSource")).OnChanged(me => me.Model.Information.Avatar);

			dependsOn.Call(me => me.OnPropertyChanged("RemainingPotentialSprintHours")).OnChanged(me => me.Model.RemainingWorkload.PotentialSprintHours);
			dependsOn.Call(me => me.OnPropertyChanged("RemainingSprintHours")).OnChanged(me => me.Model.RemainingWorkload.SprintHours);
			dependsOn.Call(me => me.OnRemainingWorkPropertyChanged("RemainingSprintWork")).OnChanged(me => me.Model.RemainingWorkload.SprintWork);
			dependsOn.Call(me => me.OnRemainingWorkPropertyChanged("RemainingUnassignedSprintWork")).OnChanged(
				me => me.Model.RemainingWorkload.UnassignedSprintWork);
			dependsOn.Call(me => me.OnRemainingWorkPropertyChanged("RemainingWorkItemCount")).OnChanged(me => me.Model.RemainingWorkload.WorkItemCount);
			dependsOn.Call(me => me.OnPropertyChanged("IncludesFreeBusyInformation")).OnChanged(me => me.Model.RemainingWorkload.IncludesFreeBusyInformation);

			dependsOn.Call(me => me.UpdateData()).OnChanged(me => me.AssignedWorkItems);
			dependsOn.Call(me => me.OnPropertyChanged("RemainingSprintWorkWithUnassigned")).OnChanged(me => me.RemainingSprintWork).OnChanged(
				me => me.RemainingUnassignedSprintWork);
			dependsOn.Call(me => me.OnPropertyChanged("BackgroundColor")).OnChanged(me => me.RemainingSprintHours).OnChanged(
				me => me.RemainingSprintWorkWithUnassigned);
		}

		public TeamMemberViewModel(ITeamMember model)
		{
			this.initialized = false;

			this.backlogItemsPieChartData = new ObservableCollection<PieChartValue<int>>();
			this.workloadPieChartData = new ObservableCollection<PieChartValue<double>>();
			for (int index = 0; index < 4; ++index)
			{
				this.backlogItemsPieChartData.Add(new PieChartValue<int>
				                                  	{
				                                  		Value = 0
				                                  	});
				this.workloadPieChartData.Add(new PieChartValue<double>
				                              	{
				                              		Value = 0.0
				                              	});
			}

			Model = model;
		}

		#region Private Fields

		private readonly ObservableCollection<PieChartValue<int>> backlogItemsPieChartData;
		private readonly ObservableCollection<PieChartValue<double>> workloadPieChartData;
		private double doneSprintWork;
		private int doneWorkItemCount;
		private bool initialized;
		private DateTime lastUpdate;
		private ITeamMember model;
		private bool notAllWorkItemsEstimated;
		private bool recentlyUpdated;

		#endregion

		#region ITeamMemberViewModel Members

		public ITeamMember Model
		{
			get { return this.model; }
			set
			{
				if (value != this.model)
				{
					this.model = value;

					OnPropertyChanged("Model");
				}
			}
		}

		public string DisplayName
		{
			get
			{
				GuardDisposed();

				return Model.DisplayName;
			}
		}

		public ImageSource AvatarImageSource
		{
			get
			{
				GuardDisposed();

				if (this.model.Information.Avatar == null)
				{
					return null;
				}

				return (ImageSource) this.model.Information.Avatar.GetRepresentation(typeof(ImageSource));
			}
		}

		public bool RecentlyUpdated
		{
			get
			{
				GuardDisposed();

				return this.recentlyUpdated;
			}
			private set
			{
				GuardDisposed();

				if (value != this.recentlyUpdated)
				{
					this.recentlyUpdated = value;

					OnPropertyChanged("RecentlyUpdated");
				}
			}
		}

		public DateTime LastUpdate
		{
			set
			{
				GuardDisposed();

				if (value != this.lastUpdate && this.initialized)
				{
					this.lastUpdate = value;

					RefreshRecentlyUpdatedFlag();
				}
			}
		}

		public double RemainingUnassignedSprintWork
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.UnassignedSprintWork;
			}
		}

		public ObservableCollection<PieChartValue<int>> BacklogItemsPieChartData
		{
			get
			{
				GuardDisposed();

				return this.backlogItemsPieChartData;
			}
		}

		public ObservableCollection<PieChartValue<double>> WorkloadPieChartData
		{
			get
			{
				GuardDisposed();

				return this.workloadPieChartData;
			}
		}

		public void RefreshRecentlyUpdatedFlag()
		{
			RecentlyUpdated = (SystemTime.Now() - this.lastUpdate).TotalMinutes < 5.0;
		}

		public IEnumerable<IWorkItemAdapter> AssignedWorkItems
		{
			get
			{
				GuardDisposed();

				return Model.AssignedWorkItems;
			}
		}

		public bool NotAllWorkItemsEstimated
		{
			get
			{
				GuardDisposed();

				return this.notAllWorkItemsEstimated;
			}
			private set
			{
				GuardDisposed();

				if (value != this.notAllWorkItemsEstimated)
				{
					this.notAllWorkItemsEstimated = value;

					OnPropertyChanged("NotAllWorkItemsEstimated");
				}
			}
		}

		public Color BackgroundColor
		{
			get
			{
				GuardDisposed();

				return
					(Color)
					new WorkTimeRatioBrushValueConverter().Convert(new object[] {RemainingSprintWorkWithUnassigned, RemainingSprintHours}, typeof(Color),
					                                               "Background", CultureInfo.InvariantCulture);
			}
		}

		public int DoneWorkItemCount
		{
			get
			{
				GuardDisposed();

				return this.doneWorkItemCount;
			}
			private set
			{
				GuardDisposed();

				if (value != this.doneWorkItemCount)
				{
					this.doneWorkItemCount = value;

					OnPropertyChanged("DoneWorkItemCount");
					MarkUpdated();
				}
			}
		}

		public double DoneSprintWork
		{
			get
			{
				GuardDisposed();

				return this.doneSprintWork;
			}
			private set
			{
				GuardDisposed();

				if (value != this.doneSprintWork)
				{
					this.doneSprintWork = value;

					OnPropertyChanged("DoneSprintWork");
					MarkUpdated();
				}
			}
		}

		public int RemainingWorkItemCount
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.WorkItemCount;
			}
		}

		public double RemainingSprintWork
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.SprintWork;
			}
		}

		public double RemainingPotentialSprintHours
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.PotentialSprintHours;
			}
		}

		public double RemainingSprintHours
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.SprintHours;
			}
		}

		public bool IncludesFreeBusyInformation
		{
			get
			{
				GuardDisposed();

				return RemainingWorkload.IncludesFreeBusyInformation;
			}
		}

		public double RemainingSprintWorkWithUnassigned
		{
			get
			{
				GuardDisposed();

				return RemainingSprintWork + RemainingUnassignedSprintWork;
			}
		}

		#endregion

		#region Private Methods

		private ITeamMemberRemainingWorkload RemainingWorkload
		{
			get { return (Model != null ? Model.RemainingWorkload : null); }
		}

		private void UpdateData()
		{
			var remainingSprintBacklogItems =
				AssignedWorkItems.Where(x => x.Type == WorkItemType.SprintBacklogItem && (x.State == WorkItemState.InProgress || x.State == WorkItemState.NotDone));

			NotAllWorkItemsEstimated = remainingSprintBacklogItems.Any(x => double.IsNaN(x.RemainingWork));

			DoneWorkItemCount = (AssignedWorkItems == null
			                     	? 0
			                     	: AssignedWorkItems.Count(x => x.Type == WorkItemType.SprintBacklogItem && x.State == WorkItemState.Done));
			DoneSprintWork = (AssignedWorkItems == null ? 0.0 : AssignedWorkItems.Where(x => x.Type == WorkItemType.SprintBacklogItem).Sum(x => x.DoneWork));

			PostAction(DispatcherPriority.DataBind, UpdatePieChartData);
		}

		private void UpdatePieChartData()
		{
			UpdateBacklogItemsPieChartData();
			UpdateWorkloadPieChartData();

			this.initialized = true;
		}

		private void UpdateBacklogItemsPieChartData()
		{
			var assignedSprintBacklogItems = Model.AssignedWorkItems.Where(x => x.Type == WorkItemType.SprintBacklogItem);
			int doneCount = 0;
			int activeCount = 0;
			int notDoneCount = 0;

			Parallel.Invoke(() => doneCount = assignedSprintBacklogItems.Count(x => x.State == WorkItemState.Done),
			                () => activeCount = assignedSprintBacklogItems.Count(x => x.State == WorkItemState.InProgress),
			                () => notDoneCount = assignedSprintBacklogItems.Count(x => x.State == WorkItemState.NotDone));

			AssignPieChartElement(BacklogItemsPieChartData, 0, doneCount);
			AssignPieChartElement(BacklogItemsPieChartData, 1, 0);
			AssignPieChartElement(BacklogItemsPieChartData, 2, activeCount);
			AssignPieChartElement(BacklogItemsPieChartData, 3, notDoneCount);
		}

		private void UpdateWorkloadPieChartData()
		{
			var assignedSprintBacklogItems = Model.AssignedWorkItems.Where(x => x.Type == WorkItemType.SprintBacklogItem);
			var activeSprintBacklogItems = assignedSprintBacklogItems.Where(x => x.State == WorkItemState.InProgress);
			double readyForTestWork = 0.0;
			double doneWorkInActiveItems = 0.0;
			double remainingWorkInActiveItems = 0.0;

			Parallel.Invoke(() => readyForTestWork = assignedSprintBacklogItems.Where(x => x.State == WorkItemState.Done).Sum(x => x.DoneWork),
			                () => doneWorkInActiveItems = activeSprintBacklogItems.Sum(x => x.DoneWork),
			                () => remainingWorkInActiveItems = activeSprintBacklogItems.Sum(x => x.RemainingWork));

			AssignPieChartElement(WorkloadPieChartData, 0, readyForTestWork);
			AssignPieChartElement(WorkloadPieChartData, 1, doneWorkInActiveItems);
			AssignPieChartElement(WorkloadPieChartData, 2, remainingWorkInActiveItems);
			AssignPieChartElement(WorkloadPieChartData, 3, RemainingSprintWorkWithUnassigned - remainingWorkInActiveItems);
		}

		private void AssignPieChartElement<T>(IList<PieChartValue<T>> collection, int index, T value)
		{
			if (!Equals(value, collection[index].Value))
			{
				collection[index].Value = value;

				MarkUpdated();
			}
		}

		private void OnRemainingWorkPropertyChanged(string propertyName)
		{
			OnPropertyChanged(propertyName);

			MarkUpdated();
		}

		private void MarkUpdated()
		{
			LastUpdate = SystemTime.Now();
		}

		#endregion
	}
}